package com.googlecode.nacl_optimizer.model;

import java.util.ArrayList;
import java.util.List;

/**
 * Block is 32-bytes instruction sequence
 * 
 * @author kornevgen
 */
public class Block
{
	protected final List<Instruction> instructions;
	
	public Block( List<Instruction> instructions )
	{
	/**
	 * @invariant:
	 */		
		 if ( instructions == null )
		     throw new IllegalStateException();
		 if ( instructions.isEmpty() )
		     throw new IllegalStateException();

		 countInstructionsLength(instructions);
		
		 if ( instructions.size() >= 2 )
		 {
    		 for( Instruction instr : instructions.subList(0, instructions.size() - 2))
    		 {
    			 if ( instr instanceof CallInstruction) throw new IllegalStateException();
    		 }
		 }
		 
        
		this.instructions = new ArrayList<Instruction>(instructions);
	}

    private void countInstructionsLength(List<Instruction> instructions) {
        /**
         * @invariant:
         */
        int sum = 0;
        for (Instruction instr : instructions) {
            sum += instr.getLength();
            if ( sum > 32 ) throw new IllegalStateException();
        }
        if ( sum != 32) throw new IllegalStateException();
    }

	/**
	 * All instructions in block
	 * @return
	 */
	public List<Instruction> getInstructions()
	{
		return instructions;
	}

    //
    //    /**
    //     * 
    //     * @param source
    //     *            mutable(!) binary program (input and output of this method)
    //     * @return number of removed blocks
    //     */
    //    public static int optimize2(Binary source)
    //    {
    //        int removed_blocks_count = 0;
    //        for (Procedure p : source.getProcedures())
    //        {
    //            while (true)
    //            {
    //                if (exists_nop_block(p))
    //                    removed_blocks_count += remove_nop_blocks(p);
    //                else if (exists_nocall_call_pair_with_long_nops(p))
    //                {
    //                    int index = find_nocall_call_pair_with_long_nops(p);
    //                    List<Instruction> new_block = exchange_pair(p, index);
    //                    move_remainder_blocks(p, new_block, index + 1);
    //                } else
    //                    break;
    //            }
    //        }
    //        return removed_blocks_count;
    //    }
    //
    //    private static void move_remainder_blocks(Procedure p,
    //            List<Instruction> new_block, int index)
    //    {
    //        assert index >= 0;
    //        assert index < p.getBlocks().size();
    //
    //        if (sum_length(new_block) == 32)
    //        {
    //            p.getBlocks().set(index, new Block(new_block));
    //        } else if (index == p.getBlocks().size() - 1 || new_block.isEmpty())
    //        {
    //            List<Instruction> b = new_block;
    //            b.add(new NopSeq(32 - sum_length(new_block), "nop"));
    //            p.getBlocks().set(index, new Block(b));
    //        } else
    //        {
    //            // with move
    //
    //            List<Instruction> next_block = p.getBlocks().get(index + 1)
    //                    .getInstructions();
    //
    //            // split next_block into 2 parts
    //            List<Instruction> next_head = new ArrayList<Instruction>();
    //            List<Instruction> next_tail = new ArrayList<Instruction>();
    //            int occupied_bytes = sum_length(new_block);
    //            assert occupied_bytes < 32;
    //
    //            for (Instruction i : next_block)
    //            {
    //                if (occupied_bytes + i.getLength() <= 32)
    //                    next_head.add(i);
    //                else
    //                    next_tail.add(i);
    //            }
    //            if (sum_length(next_head) < 32)
    //                next_head.add(new NopSeq(32 - sum_length(next_head), "nop"));
    //
    //            // merge new_block with next_head
    //            new_block.addAll(next_head);
    //            p.getBlocks().set(index, new Block(new_block));
    //            move_remainder_blocks(p, next_tail, index + 1);
    //        }
    //    }
    //
        public static int sum_length(List<Instruction> new_block)
        {
            int sum = 0;
            for( Instruction instr : new_block )
                sum += instr.getLength();
            return sum;
        }

}
